package com.dimao.certificate_build_service.handle.parallel;

import com.dimao.certificate_build_service.handle.Executor;
import com.dimao.certificate_build_service.handle.Handler;
import com.dimao.certificate_build_service.handle.ValueResult;
import io.vertx.core.Future;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Parallel implements Executor<Handler<List<ValueResult>>> {
    private static final ValueResult Empty = new ValueResult<>(false, null, null);
    private List<ValueResult> taskResultList;
    private List<Consumer<Task>> taskList;
    private Handler<List<ValueResult>> handler;

    Parallel(List<Consumer<Task>> taskList){
        this.taskList = taskList;
        taskResultList = new ArrayList<>();
        //add empty valueResult to taskResultList
        for(int i = 0; i < taskList.size(); i++){
            taskResultList.add(Empty);
        }
    }

    public static ParallelBuilder task(Consumer<Task> mainTask){
        return new ParallelBuilder(mainTask);
    }

    @Override
    public void execute(Handler<List<ValueResult>> handler) {
        this.handler = handler;
        for(int i = 0; i < taskList.size(); i++){
            taskList.get(i).accept(new ParallelTask(i));
        }
    }

    public Future<List<ValueResult>> all() {
        return Future.future(promise ->
                this.execute(results -> {
                    Throwable throwable = null;
                    for(ValueResult valueResult : results){
                        if(valueResult.failed()){
                            throwable = valueResult.cause();
                            break;
                        }
                    }
                    if(throwable != null) promise.fail(throwable);
                    else promise.complete(results);
                })
        );
    }

    public Future<List<ValueResult>> any(List<Integer> mustSucceededTaskID) {
        return Future.future(promise ->
                this.execute(results -> {
                    Throwable throwable = null;
                    for(Integer taskID : mustSucceededTaskID){
                        if(taskID > (results.size() - 1)){
                            throwable = new Throwable("TaskID Not Exists");
                            break;
                        } else if(results.get(taskID).failed()){
                            throwable = results.get(taskID).cause();
                            break;
                        }
                    }
                    if(throwable != null) promise.fail(throwable);
                    else promise.complete(results);
                })
        );
    }

    private void finish(){
        for (ValueResult valueResult : taskResultList) {
            if((valueResult != null) && valueResult.equals(Empty)) return;
        }
        //all parallel tasks is finished
        handler.handle(taskResultList);
    }

    class ParallelTask implements Task {
        private int taskID;

        ParallelTask(int taskID){
            this.taskID = taskID;
        }

        @Override
        public <T> void succeed(T v) {
            taskResultList.set(taskID, new ValueResult<>(true, v, null));
            finish();
        }

        @Override
        public void fail(Throwable throwable) {
            taskResultList.set(taskID, new ValueResult<>(false, null, throwable));
            finish();
        }

        @Override
        public void fail(String cause) {
            taskResultList.set(taskID, new ValueResult<>(false, null, new Throwable(cause)));
            finish();
        }

        @Override
        public void complete() {
            taskResultList.set(taskID, new ValueResult<>(true, null, null));
            finish();
        }
    }
}
